package com.eian.boot.common.core.utils;

import com.eian.boot.common.core.constants.CommonConstants;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Locale;

/**
 * 采用Long类型存储金额，使用BigDecimal进行金额运算。
 * <p>
 * 支持动态传入精度和舍入模式，默认采用2位小数，四舍五入。
 *
 * @author alex.meng
 * @createTime 2024-08-27 16:08
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MoneyUtils {
    private static final int DEFAULT_SCALE = 2;
    private static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_UP;
    private static final int MULTIPLIER = 100;

    /**
     * 将分转换为元(Long -> BigDecimal)
     *
     * @param fen 金额，单位分
     * @return 金额，单位元
     */
    public static BigDecimal fenToYuan(Long fen) {
        return fenToYuan(fen, DEFAULT_SCALE, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 将分转换为元(Long -> BigDecimal)
     *
     * @param fen          金额，单位分
     * @param scale        精度
     * @param roundingMode 舍入模式
     * @return 金额，单位元
     */
    public static BigDecimal fenToYuan(Long fen, int scale, RoundingMode roundingMode) {
        return BigDecimal.valueOf(fen).divide(BigDecimal.valueOf(MULTIPLIER), scale, roundingMode);
    }

    /**
     * 将元转换为分(BigDecimal -> Long)
     *
     * @param yuan 金额，单位元
     * @return 金额，单位分
     */
    public static Long yuanToFen(BigDecimal yuan) {
        return yuanToFen(yuan, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 将元转换为分(BigDecimal -> Long)
     *
     * @param yuan         金额，单位元
     * @param roundingMode 舍入模式
     * @return 金额，单位分
     */
    public static Long yuanToFen(BigDecimal yuan, RoundingMode roundingMode) {
        return yuan.multiply(BigDecimal.valueOf(MULTIPLIER)).setScale(0, roundingMode).longValue();
    }

    /**
     * 加法（以分为单位）
     *
     * @param fen1 金额1，单位分
     * @param fen2 金额2，单位分
     * @return 金额1+金额2，单位分
     */
    public static Long add(Long fen1, Long fen2) {
        return fen1 + fen2;
    }

    /**
     * 减法（以分为单位）
     *
     * @param fen1 金额1，单位分
     * @param fen2 金额2，单位分
     * @return 金额1-金额2，单位分
     */
    public static Long subtract(Long fen1, Long fen2) {
        return fen1 - fen2;
    }

    /**
     * 乘法（以分为单位）
     *
     * @param fen        金额，单位分
     * @param multiplier 乘数
     * @return 金额乘以乘数，单位分
     */
    public static Long multiply(Long fen, BigDecimal multiplier) {
        return multiply(fen, multiplier, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 乘法（以分为单位）
     *
     * @param fen          金额，单位分
     * @param multiplier   乘数
     * @param roundingMode 舍入模式
     * @return 金额乘以乘数，单位分
     */
    public static Long multiply(Long fen, BigDecimal multiplier, RoundingMode roundingMode) {
        BigDecimal result = BigDecimal.valueOf(fen).multiply(multiplier).setScale(0, roundingMode);
        return result.longValue();
    }

    /**
     * 除法（以分为单位）
     *
     * @param fen     金额，单位分
     * @param divisor 除数
     * @return 金额除以除数，单位分
     */
    public static Long divide(Long fen, BigDecimal divisor) {
        return divide(fen, divisor, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 除法（以分为单位）
     *
     * @param fen          金额，单位分
     * @param divisor      除数
     * @param roundingMode 舍入模式
     * @return 金额除以除数，单位分
     */
    public static Long divide(Long fen, BigDecimal divisor, RoundingMode roundingMode) {
        BigDecimal result = BigDecimal.valueOf(fen).divide(divisor, 0, roundingMode);
        return result.longValue();
    }

    /**
     * 格式化金额（以元为单位）
     *
     * @param fen 金额，单位分
     * @return 金额，单位分
     */
    public static String format(Long fen) {
        return format(fen, Locale.getDefault());
    }

    /**
     * 根据指定的Locale格式化金额（以元为单位）
     *
     * @param fen    金额，单位分
     * @param locale 指定的国际化语言
     * @return 金额，单位分
     */
    public static String format(Long fen, Locale locale) {
        BigDecimal yuanValue = fenToYuan(fen);
        NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(locale);
        return currencyFormat.format(yuanValue);
    }

    /**
     * 判断金额是否相等
     *
     * @param fen1 金额1，单位分
     * @param fen2 金额2，单位分
     * @return true：相等；false：不相等
     */
    public static boolean equals(Long fen1, Long fen2) {
        return fen1.equals(fen2);
    }

    /**
     * 判断金额大小
     *
     * @param fen1 金额1，单位分
     * @param fen2 金额2，单位分
     * @return -1：金额1小于金额2；0：金额1等于金额2；1：金额1大于金额2
     */
    public static int compare(Long fen1, Long fen2) {
        return fen1.compareTo(fen2);
    }

    /**
     * 检查金额是否大于0
     *
     * @param fen 金额，单位分
     * @return true：金额大于0；false：金额小于等于0
     */
    public static boolean isPositive(Long fen) {
        return fen > 0;
    }

    /**
     * 检查金额是否小于0
     *
     * @param fen 金额，单位分
     * @return true：金额小于0；false：金额大于等于0
     */
    public static boolean isNegative(Long fen) {
        return fen < 0;
    }

    /**
     * 检查金额是否为0
     *
     * @param fen 金额，单位分
     * @return true：金额为0；false：金额不为0
     */
    public static boolean isZero(Long fen) {
        return fen.equals(0L);
    }

    /**
     * 将字符串形式的元转换为分（String -> Long）
     *
     * @param yuan 金额，单位元
     * @return 金额，单位分
     */
    public static Long stringYuanToFen(String yuan) {
        BigDecimal yuanValue = new BigDecimal(yuan);
        return yuanToFen(yuanValue);
    }

    /**
     * 将double形式的元转换为分（double -> Long）
     *
     * @param yuan 金额，单位元
     * @return 金额，单位分
     */
    public static Long doubleYuanToFen(double yuan) {
        BigDecimal yuanValue = BigDecimal.valueOf(yuan);
        return yuanToFen(yuanValue);
    }

    /**
     * 将BigDecimal形式的元转换为分
     *
     * @param yuan 金额，单位元
     * @return 金额，单位分
     */
    public static Long bigDecimalYuanToFen(BigDecimal yuan) {
        return yuanToFen(yuan);
    }

    /**
     * 将金额转换为中文大写形式
     *
     * @param money 金额数字(单位分)
     * @return 中文大写金额
     */
    public static String convertToChinese(long money) {
        return convertToChinese(fenToYuan(money));
    }

    /**
     * 将金额转换为中文大写形式
     *
     * @param money 金额数字
     * @return 中文大写金额
     */
    public static String convertToChinese(BigDecimal money) {
        if (money == null) {
            return "零元整";
        }

        // 处理负数
        if (money.compareTo(BigDecimal.ZERO) < 0) {
            return "负" + convertToChinese(money.abs());
        }
        String moneyStr = money.setScale(4, RoundingMode.HALF_UP).toString();
        String[] parts = moneyStr.split("\\.");
        String integerPart = parts[0]; // 整数部分
        String decimalPart = parts.length > 1 ? parts[1] : ""; // 小数部分

        StringBuilder result = new StringBuilder();

        // 处理整数部分
        if (!"0".equals(integerPart)) {
            result.append(convertIntegerPart(integerPart)).append("元");
        } else {
            result.append("零元");
        }

        // 处理小数部分
        if (!decimalPart.isEmpty()) {
            result.append(convertDecimalPart(decimalPart));
        } else {
            result.append("整");
        }

        return result.toString();
    }

    /**
     * 转换整数部分
     *
     * @param integerPart 整数部分字符串
     * @return 中文大写整数部分
     */
    private static String convertIntegerPart(String integerPart) {
        if (integerPart == null || integerPart.isEmpty()) {
            return CommonConstants.CN_NUMBERS.getFirst();
        }

        StringBuilder result = new StringBuilder();
        int length = integerPart.length();

        for (int i = 0; i < length; i++) {
            int digit = Character.getNumericValue(integerPart.charAt(i));
            int pos = length - i - 1;

            appendDigitWithUnit(result, integerPart, i, digit, pos);
            appendGroupUnitIfNeeded(result, integerPart, i, pos);
        }

        return result.isEmpty() ? CommonConstants.CN_NUMBERS.getFirst() : result.toString();
    }

    private static void appendDigitWithUnit(StringBuilder result, String integerPart,
                                            int currentIndex, int digit, int pos) {
        if (digit != 0) {
            int unitPos = pos % 4;
            result.append(CommonConstants.CN_NUMBERS.get(digit))
                    .append(CommonConstants.CN_INTEGER_UNITS.get(unitPos));
        } else {
            handleZeroCase(result, integerPart, currentIndex);
        }
    }

    private static void handleZeroCase(StringBuilder result, String integerPart, int currentIndex) {
        if (currentIndex < integerPart.length() - 1 &&
                Character.getNumericValue(integerPart.charAt(currentIndex + 1)) != 0) {
            result.append(CommonConstants.CN_NUMBERS.getFirst());
        }
    }

    private static void appendGroupUnitIfNeeded(StringBuilder result,
                                                String integerPart, int currentIndex, int pos) {
        int groupPos = pos / 4;
        int unitPos = pos % 4;

        if (unitPos == 0 && groupPos > 0) {
            int groupValue = calculateGroupValue(integerPart, currentIndex);
            if (groupValue > 0) {
                result.append(CommonConstants.CN_GROUP_UNITS.get(groupPos));
            }
        }
    }

    private static int calculateGroupValue(String integerPart, int endIndex) {
        int groupValue = 0;
        for (int j = 0; j < 4 && endIndex - j >= 0; j++) {
            int digit = Character.getNumericValue(integerPart.charAt(endIndex - j));
            groupValue += digit * (int) Math.pow(10, j);
        }
        return groupValue;
    }

    /**
     * 转换小数部分
     *
     * @param decimalPart 小数部分字符串
     * @return 中文大写小数部分
     */
    private static String convertDecimalPart(String decimalPart) {
        StringBuilder result = new StringBuilder();
        int length = Math.min(decimalPart.length(), CommonConstants.CN_DECIMAL_UNITS.size());

        for (int i = 0; i < length; i++) {
            int digit = Character.getNumericValue(decimalPart.charAt(i));
            if (digit != 0) {
                result.append(CommonConstants.CN_NUMBERS.get(digit)).append(CommonConstants.CN_DECIMAL_UNITS.get(i));
            }
        }

        // 如果小数部分全为零，则添加"整"
        if (result.isEmpty()) {
            return "整";
        }

        return result.toString();
    }
}
